#ifndef PREFETCH_MITHRIL_HPP
#define PREFETCH_MITHRIL_HPP

#include "utils/cxx-include.hpp"
#include "utils/hash_queue.hpp"

#include "core/exception.hpp"
#include "core/log.hpp"

#include "eviction/cache-eviction.hpp"

/**
 * \file
 * \ingroup cachePrefetch
 * \brief cas::Mithril declaration.
 */

/**
 * \defgroup cachePrefetch Cache Prefetch
 * \brief Cache prefetch algorithm.
 */

namespace cas
{
    typedef uint64_t mithril_timestamp_t;

    /** \ingroup cachePrefetch */
    struct MithrilStatistic
    {
        uint64_t evictMiningTable{0u};   /**< count eviction of mining table, when mining table row length > \c maxSupport */
        uint64_t evictRecordTable{0u};   /**< count eviction of record table, when record table is full */
        uint64_t evictPrefetchTable{0u}; /**< count eviction of prefetch table, when prefetch table is full */
        uint64_t evictPrefetchList{0u};  /**< count evicted items of prefetch list, when a row is full */

        uint64_t recordRecordTable{0u}; /**< count records pushed into record table */
        uint64_t lineRecordTable{0u};   /**< count lines (rows) pushed into record table */

        uint64_t recordMiningTable{0u}; /**< count records pushed into mining table */
        uint64_t lineMiningTable{0u};   /**< count lines (rows) pushed into mining table */

        uint64_t recordPrefetchTable{0u}; /**< count records (associations) pushed into prefetch list */
        uint64_t linePrefetchTable{0u};   /**< count lines (rows) pushed into prefetch table */

        uint64_t queryPrefetchAssoc{0u};   /**< count records (associations) queried by prefetch() */
        uint64_t inplacePrefetchAssoc{0u}; /**< count prefetch records (associations) of themselves */
        uint64_t uniquePrefetchAssoc{0u};  /**< count unique records (associations) returned of prefetch() */

        uint64_t recordCount{0u};   /**< count number of record() exec */
        uint64_t miningCount{0u};   /**< count number of mining() exec */
        uint64_t prefetchCount{0u}; /**< count number of prefetch() exec */

        chrono_duration_t timeTotalRecord;   /**< time of record() exec */
        chrono_duration_t timeTotalMining;   /**< time of mining() exec */
        chrono_duration_t timeTotalPrefetch; /**< time of prefetch() exec */
    };

    /**
     * \ingroup cachePrefetch
     * Mithril and Emith prefetching algorithm wrapper.
     * \see SoCC'17 Yang MITHIRL \@ \a https://dl.acm.org/doi/10.1145/3127479.3131210
     */
    class Mithril
    {
    public:
        Mithril();
        ~Mithril();

        MithrilStatistic stat; /**< statistic */

        void init();

        void setMiningSize(const size_t &size);
        void setLookAheadRange(const uint16_t &range);
        void setParamSupport(const uint8_t &minSupport, const uint8_t &maxSupport);
        void setPrefetchSize(const uint16_t &col, const size_t &row);
        void setRecordSize(const size_t &size);

        /** \brief Mithril record. */
        void record(const CacheRequest &request);
        /** \brief Mithril prefetch. */
        std::vector<CachePage> prefetch(CacheRequest request);
        /** \brief Print statistics. */
        void print() const;

        /**
         * \brief Manually change the association between \c a and \c b
         * \param[in] a Assoc a->b
         * \param[in] b Assoc a->b
         * \param[in] scoreOffset add the score to \c a and \c b
         */
        void updateAssociation(const CachePage &a, const CachePage &b, int32_t scoreOffset);

        /**
         * \brief Mithril Mining.
         *
         * Mining procedure (Alg. 2) - sort mining table and mine, discovered
         * associations are pushed to prefetching table
         */
        void mining();

    private:
        int32_t minSupport{0}; /**< \brief minimum support, R, original 4 */
        int32_t maxSupport{0}; /**< \brief maximum support, S, orignial 8 */

        size_t recordSize{0u};      /**< \brief record table size, orignial 100000 */
        size_t miningSize{0u};      /**< \brief mining table size, N, orignial 1250 */
        size_t prefetchColSize{0u}; /**< \brief prefetch list size, P, orignial 2 */
        size_t prefetchRowSize{0u}; /**< \brief prefetch association number, orignial 11*2000=22000 */

        uint32_t lookAheadRange{0u}; /**< \brief look-ahead range, Delta, original 5 */

        std::atomic<mithril_timestamp_t> traceChronoTime{0u}; /**< Mithril timestamp */

        // mining table
        typedef std::unordered_map<CachePage, std::vector<mithril_timestamp_t>> MiningTableMap;
        typedef std::pair<CachePage, std::vector<mithril_timestamp_t>> MiningTableRow;

        MiningTableMap miningTable; /**< \brief Mithril mining table */

        // record table
        typedef hash_queue<CachePage, std::vector<mithril_timestamp_t>> RecordTableMap;
        typedef std::pair<CachePage, std::vector<mithril_timestamp_t>> RecordTableRow;

        RecordTableMap recordTable; /**< \brief Mithril record table */

        // prefetch table
        typedef hash_queue<CachePage, std::vector<CachePage>> PrefetchTableMap;
        /**
         * \brief Table for Mithril association rules.
         * An alternative implementation for inodes' \c xattrs field,
         * \c it->first can trigger prefetching of all elements in \c it->second 
         */
        PrefetchTableMap prefetchTable;

        /**
         * \brief checkAssociation (Alg. 1)
         * checks if traces in mining table indicates two blocks are associated
         * \return
         *     \c 0 - not associated
         *     \c 1 - weakly associated
         *     \c 2 - strongly associated
         */
        uint8_t checkAssociation(const std::vector<mithril_timestamp_t> &a, const std::vector<mithril_timestamp_t> &b);

        void pushAssociation(const CachePage &a, const CachePage &b);

        void moveMiningTable(const CachePage &page, const std::vector<mithril_timestamp_t> &arr, chrono_clock_t &stm);
    };
} // namespace cas

#endif /* PREFETCH_MITHRIL_HPP */
